﻿namespace PugLib.Collections
{
	using System;
	using System.Collections.Generic;
	using System.Threading;

	public sealed class SynchronizedCache<TKey, TValue> : IDisposable
	{
		private readonly Dictionary<TKey, TValue> _innerCache = new Dictionary<TKey, TValue>();
		private ReaderWriterLockSlim _cacheLock = new ReaderWriterLockSlim();

		public TValue Read(TKey key)
		{
			_cacheLock.EnterReadLock();
			try
			{
				return _innerCache[key];
			}
			finally
			{
				_cacheLock.ExitReadLock();
			}
		}

		public Boolean TryRead(TKey key, out TValue value)
		{
			_cacheLock.EnterReadLock();
			try
			{
				return _innerCache.TryGetValue(key, out value);				
			}
			finally
			{
				_cacheLock.ExitReadLock();
			}
		}

		public void Add(TKey key, TValue value)
		{
			_cacheLock.EnterWriteLock();
			try
			{
				_innerCache.Add(key, value);
			}
			finally
			{
				_cacheLock.ExitWriteLock();
			}
		}

		public bool AddWithTimeout(TKey key, TValue value, int timeout)
		{
			if (_cacheLock.TryEnterWriteLock(timeout))
			{
				try
				{
					_innerCache.Add(key, value);
				}
				finally
				{
					_cacheLock.ExitWriteLock();
				}
				return true;
			}
			return false;
		}

		public AddOrUpdateStatus AddOrUpdate(TKey key, TValue value)
		{
			_cacheLock.EnterUpgradeableReadLock();
			try
			{
				TValue result;
				if (_innerCache.TryGetValue(key, out result))
				{
					if (result.Equals(value))
					{
						return AddOrUpdateStatus.Unchanged;
					}
					else
					{
						_cacheLock.EnterWriteLock();
						try
						{
							_innerCache[key] = value;
						}
						finally
						{
							_cacheLock.ExitWriteLock();
						}
						return AddOrUpdateStatus.Updated;
					}
				}
				else
				{
					_cacheLock.EnterWriteLock();
					try
					{
						_innerCache.Add(key, value);
					}
					finally
					{
						_cacheLock.ExitWriteLock();
					}
					return AddOrUpdateStatus.Added;
				}
			}
			finally
			{
				_cacheLock.ExitUpgradeableReadLock();
			}
		}

		public void Delete(TKey key)
		{
			_cacheLock.EnterWriteLock();
			try
			{
				_innerCache.Remove(key);
			}
			finally
			{
				_cacheLock.ExitWriteLock();
			}
		}

		#region Implementation of IDisposable
		public void Dispose()
		{
			if (_cacheLock != null)
			{
				_cacheLock.Dispose();
				_cacheLock = null;
			}
			GC.SuppressFinalize(this);
		}
		#endregion
	}

	public enum AddOrUpdateStatus
	{
		Added,
		Updated,
		Unchanged
	} ;
}